AWS CloudFormationのコンソールでAWS CDKのConstruct Treeが見れるようになりました

AWS CloudFormationのコンソールでAWS CDKのConstruct Treeが見れるようになりました

Clock Icon2022.09.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部 IoT事業部の若槻です。

このたびのアップデートにより、AWS CloudFormationのコンソールでAWS CDKConstruct Treeが見れるようになったとのことなので、確認してみました。

確認してみた

次のようなCDK Stackをデプロイします。Stack内で明示的に使用しているConstructは3つです。

import {
  aws_events,
  aws_events_targets,
  aws_stepfunctions,
  aws_stepfunctions_tasks,
  Stack,
  StackProps,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class AwsCdkAppStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // Lambda実行タスク
    const someTask = new aws_stepfunctions_tasks.EvaluateExpression(
      this,
      'someTask',
      {
        expression: '{"key1": "val1"}',
      }
    );

    // ステートマシン
    const myStateMachine = new aws_stepfunctions.StateMachine(
      this,
      'myStateMachine',
      {
        stateMachineName: 'myStateMachine',
        definition: someTask,
      }
    );

    // ステートマシン実行ルール
    new aws_events.Rule(this, 'rule', {
      ruleName: 'rule',
      schedule: aws_events.Schedule.cron({ minute: '0' }),
      targets: [new aws_events_targets.SfnStateMachine(myStateMachine)],
    });
  }
}

CloudFormationのコンソールでStackのResource一覧を見ると、[Tree view]と[Flat view]が切り替えられるようになっています。今表示しているのはTree viewが畳まれた状態です。

Tree Viewを展開した様子です。展開した2階層目にdefault childとなるResourceが配置され、さらに展開した3階層目以降にdefault child以外のIAMなどのResourceが配置されており、Construct Treeが分かりやすく表示されています!!!

Flat Viewだと今まであった見慣れた表示となります。

補足:Construct Treeについて

AWS CDK appでは、すべてのConstructに上位のConstructのスコープ引数が渡されることにより階層構造が作成され、これをConstruct Treeと呼びます。

Construct TreeのRootはApp classのインスタンスです。その配下にStackがあり、さらにその配下にConstructがTreeを成していきます。

Treeの各Nodeの直下のchildrenはnode.childrenで取得が可能です。実際に取得してみます。

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AwsCdkAppStack } from '../lib/aws-cdk-app-stack';

const app = new cdk.App();
const awsCdkAppStack = new AwsCdkAppStack(app, 'AwsCdkAppStack');

console.log(app.node.children); //App直下のchildrenを取得
console.log(awsCdkAppStack.node.children); //awsCdkAppStack直下のchildrenを取得

まずapp.node.childrenの出力結果を見ると、TreeMetadataAwsCdkAppStackが直下にあり、AwsCdkAppStackにはさらにchildがあることが分かります。

 [
  <ref *1> TreeMetadata {
    node: Node {
      host: [Circular *1],
      _locked: false,
      _children: {},
      _context: {},
      _metadata: [],
      _dependencies: Set(0) {},
      _validations: [],
      id: 'Tree',
      scope: [App]
    },
    [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] }
  },
  <ref *2> AwsCdkAppStack {
    node: Node {
      host: [Circular *2],
      _locked: false,
      _children: [Object],
      _context: {},
      _metadata: [],
      _dependencies: Set(0) {},
      _validations: [],
      id: 'AwsCdkAppStack',
      scope: [App]
    },
    _missingContext: [],
    _stackDependencies: {},
    templateOptions: {},
    _logicalIds: LogicalIDs { renames: {}, reverse: {} },
    account: '${Token[AWS.AccountId.6]}',
    region: '${Token[AWS.Region.10]}',
    environment: 'aws://unknown-account/unknown-region',
    terminationProtection: undefined,
    _stackName: 'AwsCdkAppStack',
    tags: TagManager {
      tags: Map(0) {},
      priorities: Map(0) {},
      initialTagPriority: 50,
      resourceTypeName: 'aws:cdk:stack',
      tagFormatter: KeyValueFormatter {},
      tagPropertyName: 'tags',
      renderedTags: [LazyAny]
    },
    artifactId: 'AwsCdkAppStack',
    templateFile: 'AwsCdkAppStack.template.json',
    _versionReportingEnabled: true,
    synthesizer: DefaultStackSynthesizer {
      props: {},
      assetManifest: [AssetManifestBuilder],
      useLookupRoleForStackOperations: true,
      _stack: [Circular *2],
      qualifier: 'hnb659fds',
      bucketName: 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}',
      repositoryName: 'cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}',
      _deployRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}',
      _cloudFormationExecutionRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}',
      fileAssetPublishingRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}',
      imageAssetPublishingRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}',
      lookupRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}',
      bucketPrefix: '',
      dockerTagPrefix: '',
      bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version'
    },
    [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] }
  }
]

awsCdkAppStack.node.childrenの出力結果を見ると見ると配下に4つのConstructがあることが分かります。

[
  <ref *1> EvaluateExpression {
    node: Node {
      host: [Circular *1],
      _locked: false,
      _children: [Object],
      _context: {},
      _metadata: [],
      _dependencies: Set(0) {},
      _validations: [Array],
      id: 'someTask',
      scope: [AwsCdkAppStack]
    },
    branches: [],
    retries: [],
    catches: [],
    choices: [],
    prefixes: [],
    incomingStates: [],
    startState: [Circular *1],
    comment: undefined,
    inputPath: undefined,
    parameters: undefined,
    outputPath: undefined,
    resultPath: undefined,
    resultSelector: undefined,
    endStates: [ [Circular *1] ],
    timeout: undefined,
    heartbeat: undefined,
    props: { expression: '{"key1": "val1"}' },
    evalFn: SingletonFunction {
      node: [Node],
      stack: [AwsCdkAppStack],
      env: [Object],
      _physicalName: undefined,
      _allowCrossEnvironment: false,
      physicalName: '${Token[TOKEN.230]}',
      _warnIfCurrentVersionCalled: false,
      _invocationGrants: {},
      _functionUrlInvocationGrants: {},
      lambdaFunction: [Function],
      permissionsNode: [Node],
      architecture: [Architecture],
      functionArn: '${Token[TOKEN.250]}',
      functionName: '${Token[TOKEN.249]}',
      role: [Role],
      runtime: [Runtime],
      grantPrincipal: [Role],
      canCreatePermissions: true,
      [Symbol(@aws-cdk/core.DependableTrait)]: [Object]
    },
    taskPolicies: [ [PolicyStatement] ],
    containingGraph: StateGraph {
      startState: [Circular *1],
      graphDescription: 'State Machine myStateMachine definition',
      policyStatements: [Array],
      allStates: [Set],
      allContainedStates: [Map],
      timeout: undefined
    },
    [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] }
  },
  <ref *2> Function {
    node: Node {
      host: [Circular *2],
      _locked: false,
      _children: [Object],
      _context: {},
      _metadata: [],
      _dependencies: Set(0) {},
      _validations: [],
      id: 'Evalda2d1181604e4a4586941a6abd7fe42d',
      scope: [AwsCdkAppStack]
    },
    stack: AwsCdkAppStack {
      node: [Node],
      _missingContext: [],
      _stackDependencies: {},
      templateOptions: {},
      _logicalIds: [LogicalIDs],
      account: '${Token[AWS.AccountId.9]}',
      region: '${Token[AWS.Region.13]}',
      environment: 'aws://unknown-account/unknown-region',
      terminationProtection: undefined,
      _stackName: 'AwsCdkAppStack',
      tags: [TagManager],
      artifactId: 'AwsCdkAppStack',
      templateFile: 'AwsCdkAppStack.template.json',
      _versionReportingEnabled: true,
      synthesizer: [DefaultStackSynthesizer],
      [Symbol(@aws-cdk/core.DependableTrait)]: [Object]
    },
    env: {
      account: '${Token[AWS.AccountId.9]}',
      region: '${Token[AWS.Region.13]}'
    },
    _physicalName: undefined,
    _allowCrossEnvironment: false,
    physicalName: '${Token[TOKEN.231]}',
    _warnIfCurrentVersionCalled: false,
    _invocationGrants: {},
    _functionUrlInvocationGrants: {},
    permissionsNode: Node {
      host: [Circular *2],
      _locked: false,
      _children: [Object],
      _context: {},
      _metadata: [],
      _dependencies: Set(0) {},
      _validations: [],
      id: 'Evalda2d1181604e4a4586941a6abd7fe42d',
      scope: [AwsCdkAppStack]
    },
    canCreatePermissions: true,
    _layers: [],
    environment: {},
    role: <ref *3> Role {
      node: [Node],
      stack: [AwsCdkAppStack],
      env: [Object],
      _physicalName: undefined,
      _allowCrossEnvironment: false,
      physicalName: '${Token[TOKEN.232]}',
      grantPrincipal: [Circular *3],
      principalAccount: '${Token[AWS.AccountId.9]}',
      assumeRoleAction: 'sts:AssumeRole',
      managedPolicies: [Array],
      attachedPolicies: [AttachedPolicies],
      dependables: Map(0) {},
      _didSplit: false,
      assumeRolePolicy: [PolicyDocument],
      inlinePolicies: {},
      permissionsBoundary: undefined,
      roleId: '${Token[TOKEN.237]}',
      roleArn: '${Token[TOKEN.238]}',
      roleName: '${Token[TOKEN.240]}',
      policyFragment: [PrincipalPolicyFragment],
      [Symbol(@aws-cdk/core.DependableTrait)]: [Object]
    },
    grantPrincipal: <ref *3> Role {
      node: [Node],
      stack: [AwsCdkAppStack],
      env: [Object],
      _physicalName: undefined,
      _allowCrossEnvironment: false,
      physicalName: '${Token[TOKEN.232]}',
      grantPrincipal: [Circular *3],
      principalAccount: '${Token[AWS.AccountId.9]}',
      assumeRoleAction: 'sts:AssumeRole',
      managedPolicies: [Array],
      attachedPolicies: [AttachedPolicies],
      dependables: Map(0) {},
      _didSplit: false,
      assumeRolePolicy: [PolicyDocument],
      inlinePolicies: {},
      permissionsBoundary: undefined,
      roleId: '${Token[TOKEN.237]}',
      roleArn: '${Token[TOKEN.238]}',
      roleName: '${Token[TOKEN.240]}',
      policyFragment: [PrincipalPolicyFragment],
      [Symbol(@aws-cdk/core.DependableTrait)]: [Object]
    },
    _architecture: undefined,
    functionName: '${Token[TOKEN.249]}',
    functionArn: '${Token[TOKEN.250]}',
    runtime: Runtime {
      name: 'nodejs14.x',
      supportsInlineCode: true,
      family: 0,
      bundlingDockerImage: [DockerImage],
      bundlingImage: [DockerImage],
      supportsCodeGuruProfiling: false
    },
    timeout: undefined,
    architecture: Architecture { name: 'x86_64', dockerPlatform: 'linux/amd64' },
    currentVersionOptions: undefined,
    [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] }
  },
  <ref *4> StateMachine {
    node: Node {
      host: [Circular *4],
      _locked: false,
      _children: [Object],
      _context: {},
      _metadata: [],
      _dependencies: Set(0) {},
      _validations: [],
      id: 'myStateMachine',
      scope: [AwsCdkAppStack]
    },
    stack: AwsCdkAppStack {
      node: [Node],
      _missingContext: [],
      _stackDependencies: {},
      templateOptions: {},
      _logicalIds: [LogicalIDs],
      account: '${Token[AWS.AccountId.9]}',
      region: '${Token[AWS.Region.13]}',
      environment: 'aws://unknown-account/unknown-region',
      terminationProtection: undefined,
      _stackName: 'AwsCdkAppStack',
      tags: [TagManager],
      artifactId: 'AwsCdkAppStack',
      templateFile: 'AwsCdkAppStack.template.json',
      _versionReportingEnabled: true,
      synthesizer: [DefaultStackSynthesizer],
      [Symbol(@aws-cdk/core.DependableTrait)]: [Object]
    },
    env: {
      account: '${Token[AWS.AccountId.9]}',
      region: '${Token[AWS.Region.13]}'
    },
    _physicalName: 'myStateMachine',
    _allowCrossEnvironment: true,
    physicalName: 'myStateMachine',
    role: <ref *5> Role {
      node: [Node],
      stack: [AwsCdkAppStack],
      env: [Object],
      _physicalName: undefined,
      _allowCrossEnvironment: false,
      physicalName: '${Token[TOKEN.251]}',
      grantPrincipal: [Circular *5],
      principalAccount: '${Token[AWS.AccountId.9]}',
      assumeRoleAction: 'sts:AssumeRole',
      managedPolicies: [],
      attachedPolicies: [AttachedPolicies],
      dependables: [Map],
      _didSplit: false,
      assumeRolePolicy: [PolicyDocument],
      inlinePolicies: {},
      permissionsBoundary: undefined,
      roleId: '${Token[TOKEN.256]}',
      roleArn: '${Token[TOKEN.257]}',
      roleName: '${Token[TOKEN.259]}',
      policyFragment: [PrincipalPolicyFragment],
      defaultPolicy: [Policy],
      [Symbol(@aws-cdk/core.DependableTrait)]: [Object]
    },
    stateMachineType: 'STANDARD',
    stateMachineName: '${Token[TOKEN.269]}',
    stateMachineArn: '${Token[TOKEN.271]}',
    [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] }
  },
  <ref *6> Rule {
    node: Node {
      host: [Circular *6],
      _locked: false,
      _children: [Object],
      _context: {},
      _metadata: [],
      _dependencies: Set(0) {},
      _validations: [Array],
      id: 'rule',
      scope: [AwsCdkAppStack]
    },
    stack: AwsCdkAppStack {
      node: [Node],
      _missingContext: [],
      _stackDependencies: {},
      templateOptions: {},
      _logicalIds: [LogicalIDs],
      account: '${Token[AWS.AccountId.9]}',
      region: '${Token[AWS.Region.13]}',
      environment: 'aws://unknown-account/unknown-region',
      terminationProtection: undefined,
      _stackName: 'AwsCdkAppStack',
      tags: [TagManager],
      artifactId: 'AwsCdkAppStack',
      templateFile: 'AwsCdkAppStack.template.json',
      _versionReportingEnabled: true,
      synthesizer: [DefaultStackSynthesizer],
      [Symbol(@aws-cdk/core.DependableTrait)]: [Object]
    },
    env: {
      account: '${Token[AWS.AccountId.9]}',
      region: '${Token[AWS.Region.13]}'
    },
    _physicalName: 'rule',
    _allowCrossEnvironment: true,
    physicalName: 'rule',
    targets: [ [Object] ],
    eventPattern: {},
    _xEnvTargetsAdded: Set(0) {},
    description: undefined,
    scheduleExpression: 'cron(0 * * * ? *)',
    ruleArn: '${Token[TOKEN.288]}',
    ruleName: '${Token[TOKEN.290]}',
    [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] }
  }
]

このようにしてコマンドを使ってもCDKのConstruct Treeをたどることはできます。それが今回のアップデートによりマネジメントコンソール上で見れるようになり、例えば複雑な参照関係のあるCDK Appでのデプロイ失敗時などにデバッグを行う際などに役に立ちそうです。なかなか嬉しいアップデートでした。

参考

以上

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.